#include <apr_uri.h>
#include <apr_strings.h>
#include <ctype.h>
#include "common.h"

static void setstringnil(lua_State *L, const char *key, const char *val);
static int lua_apr_uri_parsequery(lua_State *L);
static char* decode_rawurl(apr_pool_t *pool, char *str, int len);

/** Parse a URL and return the parts as a table
 * \luaparam input the URL string to parse
 * \luareturn the table of url parts with keys (scheme, user, password, host, port, path, query, fragment, query_parsed)
 */
static int lua_apr_uri_parse(lua_State *L) {
    apr_status_t rv;
    const char *input = luaL_checkstring(L, 1);
    apr_pool_t *tmp_pool;
    apr_uri_t uri;
    
    rv = apr_pool_create(&tmp_pool, NULL);
    if(rv != APR_SUCCESS) {
        return luaL_error(L, "An unexpected error occurred while parsing the URI");
    }
    
    rv = apr_uri_parse(tmp_pool, input, &uri);
    if(rv != APR_SUCCESS) {
        return luaL_error(L, "An unexpected error occurred while parsing the URI");
    }
    
    lua_createtable(L, 0, 9);
    setstringnil(L, "scheme", uri.scheme);
    setstringnil(L, "user", uri.user);
    setstringnil(L, "password", uri.password);
    setstringnil(L, "host", uri.hostname);

    lua_pushstring(L, "port");
    if(uri.port_str == NULL) {
        lua_pushnil(L);
        lua_settable(L, -3);
    } else {
        lua_pushinteger(L, uri.port);
        lua_settable(L, -3);
    }
    
    setstringnil(L, "path", uri.path);
    setstringnil(L, "query", uri.query);
    setstringnil(L, "fragment", uri.fragment);
    
    lua_pushstring(L, "query_parsed");
    if(uri.query == NULL) {
        lua_pushnil(L);
    } else {
        lua_pushcfunction(L, lua_apr_uri_parsequery);
        lua_pushstring(L, uri.query);
        lua_call(L, 1, 1);
    }
    
    lua_settable(L, -3);
    
    apr_pool_destroy(tmp_pool);
    return 1;
}

/** Parse a query string and return the parts as a table
 * \luaparam input the query string
 * \luareturn the query string with the variables deparsed and returned as a table
 */
static int lua_apr_uri_parsequery(lua_State *L) {
    char *tok, *last, *iter, *valstart, *copyin;
    apr_size_t input_len;
    int place = 0, encoded = 0, toklen = 0;
    const char *input = luaL_checklstring(L, 1, &input_len);
    apr_pool_t *tmp_pool;
    apr_status_t rv;
    
    rv = apr_pool_create(&tmp_pool, NULL);
    if(rv != APR_SUCCESS) {
        return luaL_error(L, "Unexpected error parsing query");
    }
    
    copyin = apr_pstrmemdup(tmp_pool, input, input_len);
    lua_newtable(L);
    tok = apr_strtok(copyin, "&", &last);
    while(tok != NULL) {
        iter = tok;
        place = 0;
        toklen = 0;
        valstart = NULL;
        
        while(*iter != '\0') {
            switch(*iter) {
            case '=':
                if(place == 0) {
                    /*Push the name as a key to the table*/
                    if(encoded) {
                        lua_pushstring(L, decode_rawurl(tmp_pool, tok, toklen));
                    } else {
                        lua_pushlstring(L, tok, toklen);
                    }
                    valstart = iter + 1;
                    toklen = 0;
                    encoded = 0;
                }
                place = 1;
                break;
            case '+':
                *iter = ' ';
                break;
            case '%':
                encoded = 1;
                break;
            }
            
            toklen++;
            iter++;
        }
        
        /*Push the value and set*/
        if(valstart == NULL) {
            lua_pushstring(L, "");
        } else {
            if(encoded) {
                lua_pushstring(L, decode_rawurl(tmp_pool, valstart, toklen));
            } else {
                lua_pushstring(L, valstart);
            }
        }
        lua_settable(L, -3);
        
        /*Get the next token*/
        tok = apr_strtok(NULL, "&", &last);
    }
    
    apr_pool_destroy(tmp_pool);
    return 1;
}

/** Raw URL decoder utility function used by the query string parser
 * \param pool the pool to use for memory allocation
 * \param str the string to decode
 * \param len the length of str
 * \return the decoded string
 */
static char* decode_rawurl(apr_pool_t *pool, char *str, int len) {
    /*Decoded URL values will never be smaller than the value itself*/
    char *out = (char *) apr_palloc(pool, len + 1);
    char *iterout = out;
    char tmp_char;
    int i;
    
    for(i=0; i<len; i++) {
        if(*str == '%') {
            /*If the next two digits are hex values, convert them to a character and append it to the output string*/
            if(len - i > 2 && isxdigit(*(str + 1)) && isxdigit(*(str + 2))) {
                str++;
                tmp_char = *(str + 2);
                *(str + 2) = '\0';
                *iterout++ = (char) strtol(str, (char **) NULL, 16);
                str += 2;
                i += 3;
                *str = tmp_char;
            }
        } else {
            *iterout = *str;
            iterout++;
        }
            
        str++;
    }
    
    *iterout = '\0';
    return out;
}

/** Simple helper function that either pushes nil or a string value depending on the presence of val
 * \param L the lua state to set table values from
 * \param key the key value to set on the table
 * \param val a string value to set, or NULL to set a nil value
 */
static void setstringnil(lua_State *L, const char *key, const char *val) {
    lua_pushstring(L, key);
    if(val == NULL) {
        lua_pushnil(L);
    } else {
        lua_pushstring(L, val);
    }
    
    lua_settable(L, -3);
}

static const struct luaL_reg lua_apr_uri [] = {
    {"parse", lua_apr_uri_parse},
    {"parse_query", lua_apr_uri_parsequery},
    {NULL, NULL}
};

int luaopen_apr_uri(lua_State *L) {
    apr_status_t rv;
    luaL_register(L, "apr.uri", lua_apr_uri);
    
    rv = apr_initialize();
    if(rv != APR_SUCCESS) {
        return luaL_error(L, "Error initializing APR");
    }
    
    return 1;
}
